﻿using System;
using System.Linq;
using System.Collections.Generic;
using System.Text;
using Calculator.Models;
using System.Diagnostics;
using System.Globalization;

namespace Calculator
{
    /// <summary>
    /// Perform the logic behind the calculator.
    /// This is a model designed as state machine.
    /// </summary>
    public class CalcStateMachine
    {
        private bool useOrderOfOperations = true;
        private Expression expression;
        private Expression unaryParent;
        private Expression binaryParent;
        private Stack<Expression> bracketStack = new Stack<Expression>();
        private readonly string LocalizedPoint = CultureInfo.CurrentUICulture.NumberFormat.NumberDecimalSeparator;

        public CalcStateMachine()
            : base()
        {
            ClearAll();
        }

        public bool Hyp { get; set; }
        public bool Inv { get; set; }

        /// <summary>
        /// Gets the current result.
        /// </summary>
        public string Result { get; private set; }

        public double ResultValue
        {
            get
            {
                try
                {
                    return double.Parse(Result);
                }
                catch
                {
                    return double.MaxValue;
                }
            }
        }

        /// <summary>
        /// Gets the current formula.
        /// </summary>
        public string Formula { get; private set; }

        protected Expression Unary
        {
            get { return unaryParent.Right; }
            set { unaryParent.Right = value; }
        }

        protected Expression Binary
        {
            get { return binaryParent.Right; }
            set { binaryParent.Right = value; }
        }


        public void AddToken(string token)
        {
            if (IsDigit(token))
            {
                UpdateDigit(token);
            }
            else
            {
                switch (token.ToLower())
                {
                    case "hyp":
                        Hyp ^= true;
                        break;

                    case "inv":
                        Inv ^= true;
                        break;

                    case "pi":
                    case "π": SetPi(); break;

                    case "(": PushStack(); break;
                    case ")": PopStack(); break;

                    case "ce": ClearCurrent(); break;
                    case "c": ClearAll(); break;
                    case "": Undo(1); break;

                    case "±":
                    case "+/-": Negate(); break;

                    case "+": BinaryOp(ExpressionOp.Add); break;
                    case "-": BinaryOp(ExpressionOp.Substract); break;
                    case "*": BinaryOp(ExpressionOp.Multiply); break;
                    case "/":
                    case "÷": BinaryOp(ExpressionOp.Divide); break;

                    case "1/x": UnaryOp(ExpressionOp.Reciproc); break;
                    case "=": Equals(); break;

                    case "!": UnaryOp(ExpressionOp.Faculty); break;
                    case "log": UnaryOp(ExpressionOp.Log); break;
                    case "ln": UnaryOp(ExpressionOp.Ln); break;

                    case "ex": UnaryOp(ExpressionOp.Exp); break;

                    case "sqrt":
                    case "√": UnaryOp(ExpressionOp.Sqrt); break;

                    case "^":
                    case "xy": BinaryOp(ExpressionOp.Pow); break;

                    case "x³":
                    case "³": UnaryOp(ExpressionOp.Cube); break;

                    case "sqr":
                    case "x²":
                    case "²": UnaryOp(ExpressionOp.Square); break;

                    case "sin": SinOp(); break;
                    case "cos": CosOp(); break;
                    case "tan": TanOp(); break;
                }
            }
        }

        private void TanOp()
        {
            ExpressionOp op = ExpressionOp.Tan;
            if (Inv & Hyp) op = ExpressionOp.ATanH;
            else if (Inv) op = ExpressionOp.ATan;
            else if (Hyp) op = ExpressionOp.TanH;
            UnaryOp(op);
        }

        private void CosOp()
        {
            ExpressionOp op = ExpressionOp.Cos;
            if (Inv & Hyp) op = ExpressionOp.ACosH;
            else if (Inv) op = ExpressionOp.ACos;
            else if (Hyp) op = ExpressionOp.CosH;
            UnaryOp(op);
        }

        private void SinOp()
        {
            ExpressionOp op = ExpressionOp.Sin;
            if (Inv & Hyp) op = ExpressionOp.ASinH;
            else if (Inv) op = ExpressionOp.ASin;
            else if (Hyp) op = ExpressionOp.SinH;
            UnaryOp(op);
        }

        private static bool IsDigit(string token)
        {
            return token.Length == 1 && "E0123456789.".Contains(token[0]);
        }

        private void UpdateDigit(string token)
        {
            if ((token == "E" || token == ".") && Result.Contains('E')) return;
            if (token == ".") token = LocalizedPoint;
            bool append = IsAppending();
            if (append)
            {
                if (token == LocalizedPoint && Result.Contains(token)) return;
                Result += token;
            }
            else
            {
                Result = token;
            }
            Unary = new Expression(ResultValue);
            UpdateFormula();
        }

        private bool IsAppending()
        {
            return Unary != null && Unary.Op == ExpressionOp.Constant;
        }

        private void SetPi()
        {
            Unary = new Expression(ExpressionOp.Pi, null);
            Result = Unary.Calculate(useOrderOfOperations).ToString();
            UpdateFormula();
        }

        private void UpdateFormula()
        {
            Formula = expression.ToString();
        }

        private void ClearCurrent()
        {
            Result = "0";
            Unary = null;
        }

        private void Equals()
        {
            if (Unary == null) Unary = new Expression(ResultValue);
            ResolveBracketStack();
            Result = expression.Calculate(useOrderOfOperations).ToString();
            Formula = expression.ToString() + " =";
            expression = new Expression();
            unaryParent = binaryParent = expression;
            expression.Right = new Expression(ResultValue);
        }

        private void ResolveBracketStack()
        {
            while (bracketStack.Count > 0)
            {
                binaryParent.Op = ExpressionOp.Bracket;
                bracketStack.Pop();
            }
            unaryParent = binaryParent = expression;
            UpdateFormula();
            Result = expression.Value.ToString();
        }

        private void PushStack()
        {
            bracketStack.Push(binaryParent);
            Expression bracket = new Expression(ExpressionOp.OpenBracket, null);
            Unary = bracket;
            binaryParent = bracket;
            unaryParent = bracket;
            UpdateFormula();
            Result = "0";
        }

        private void PopStack()
        {
            if (bracketStack.Count > 0)
            {
                binaryParent.Op = ExpressionOp.Bracket;
                Expression e = bracketStack.Pop();
                unaryParent = binaryParent = e;
                UpdateFormula();
                Result = expression.Value.ToString();
            }
        }

        private void Negate()
        {
            if (Result.Contains("E")) NegateExponent();
            else NegateMantisse();
            Unary = new Expression(ResultValue);
            UpdateFormula();
        }

        private void NegateExponent()
        {
            int index = Result.IndexOf('E');
            string exponent = Result.Substring(index + 1);
            string mantisse = Result.Substring(0, index + 1);
            if (exponent.Length > 0)
            {
                char c = exponent[0];
                switch (c)
                {
                    case '-': exponent = "+" + exponent.Substring(1); break;
                    case '+': exponent = "-" + exponent.Substring(1); break;
                    default: exponent = "-" + exponent; break;
                }
                Result = mantisse + exponent;
            }
            else Result += "-";
        }

        private void NegateMantisse()
        {
            if (Result.Length == 0)
            {
                Result = "-0";
            }
            else
            {
                Result = Result[0] == '-' ? Result.Substring(1) : "-" + Result;
            }
        }

        private void UnaryOp(ExpressionOp op)
        {
            Expression u = Unary ?? new Expression(ResultValue);
            Unary = new Expression(op, Unary);

            UpdateValues();
        }

        private void BinaryOp(ExpressionOp op)
        {
            if (Unary == null) Unary = new Expression(ResultValue);
            unaryParent = Binary = new Expression(op, Binary, null);
            UpdateValues();
        }

        private void UpdateValues()
        {
            UpdateFormula();

            Expression e = Binary;
            if (e != null && e.Left != null) e = e.Left;
            Result = e != null ? e.Calculate(useOrderOfOperations).ToString() : "0";
        }

        private void Undo(int numberOfChars)
        {
            if (Result.Length > 0)
            {
                Result = Result.Substring(0, Result.Length - 1);
            }
        }

        private void ClearAll()
        {
            bracketStack.Clear();
            expression = new Expression();
            unaryParent = expression;
            binaryParent = expression;
            Result = "0";
            Formula = string.Empty;
        }
    }
}
